package nachos.threads;

import nachos.machine.*;

import java.util.*;

/**
 * Uses the hardware timer to provide preemption, and to allow threads to sleep
 * until a certain time. 
 */
public class Alarm {
	PriorityQueue<TimeThread> waitTimes;
    /**
     * Allocate a new Alarm. Set the machine's timer interrupt handler to this
     * alarm's callback.
     *
     * <p><b>Note</b>: Nachos will not function correctly with more than one
     * alarm.
     */
    public Alarm() {
    	waitTimes = new PriorityQueue<TimeThread>();
    	
    	Machine.timer().setInterruptHandler(new Runnable() {
    		public void run() { timerInterrupt(); }
	    });
    }

    /**
     * The timer interrupt handler. This is called by the machine's timer
     * periodically (approximately every 500 clock ticks). Causes the current
     * thread to yield, forcing a context switch if there is another thread
     * that should be run.
     */
    public void timerInterrupt() {
    	//KThread.currentThread().yield();
    	if(waitTimes == null)
    		waitTimes = new PriorityQueue<TimeThread>();
    	
    	boolean initStatus = Machine.interrupt().disable();
    	
    	while(!waitTimes.isEmpty() && waitTimes.peek().time <= Machine.timer().getTime()) {
    		KThread temp = waitTimes.poll().thread;
    		temp.ready();
    		if (Lib.test('m'))
    			System.out.println(temp.getName() + " was removed from waiting queue at " + Machine.timer().getTime());
    	}
    	Machine.interrupt().restore(initStatus);
    	KThread.yield();
    }

    /**
     * Put the current thread to sleep for at least <i>x</i> ticks,
     * waking it up in the timer interrupt handler. The thread must be
     * woken up (placed in the scheduler ready set) during the first timer
     * interrupt where
     *
     * <p><blockquote>
     * (current time) >= (WaitUntil called time)+(x)
     * </blockquote>
     *
     * @param	x	the minimum number of clock ticks to wait.
     *
     * @see	nachos.machine.Timer#getTime()
     */
    public void waitUntil(long x) {
		long wakeTime = Machine.timer().getTime() + x;
		boolean initStatus = Machine.interrupt().disable();

		TimeThread currentTT = new TimeThread(wakeTime, KThread.currentThread());
		if(waitTimes == null)
    		waitTimes = new PriorityQueue<TimeThread>();
		waitTimes.add(currentTT);
		KThread.sleep();
		Machine.interrupt().restore(initStatus);
		//timerInterrupt();
    }

    private class TimeThread implements Comparable<TimeThread> {
    	protected Long time;
    	protected KThread thread;

    	TimeThread (long time, KThread thread) {
            this.time = time;
            this.thread = thread;
        }
		public int compareTo(TimeThread tt) {
			return time.compareTo(tt.time);
		}
    }
    
    public static void selfTest() {
        System.out.println("****START ALARM TESTING****");
        
        final Alarm alarm = new Alarm();
        
        KThread t1 = new KThread(new Runnable() {
	        public void run() {
	            for (int k=0; k<6000; k++) {
	                if (k % 1000 == 0)
	                    System.out.println("T1 at " + k / 1000);
	                else if (k == 1499) {
	                    System.out.println("putting T1 to waiting queue until 1000 ticks at "+Machine.timer().getTime());
	                    alarm.waitUntil(1000);
	                }
	            }
	        }
	    });
        
        KThread t2 = new KThread(new Runnable() {
            public void run() {
            	for (int k = 0; k<5000; k++)
                    if (k % 1000 == 0) {
                        System.out.println("T2 at "+ k / 1000);
                    }
                    else if (k == 2499) {
						System.out.println("putting T2 to waiting queue until 200 ticks at "+Machine.timer().getTime());
						alarm.waitUntil(200);
                    }
            }
        });
        
        KThread t3 = new KThread(new Runnable() {
            public void run() {
            	for (int k = 0; k<2500; k++)
                    if (k % 1000 == 0) {
                        System.out.println("T3 at "+ k / 1000);
                    } else if (k==1501) {
                        System.out.println("putting T3 in queue at "+Machine.timer().getTime()+" for 0 ticks");
                        alarm.waitUntil(0);
                    }
            }
        });
        t1.setName("T1");
        t2.setName("T2");
        t3.setName("T3");
        
        t1.fork();
        t2.fork();
        t3.fork();
        
        t1.join();
        t2.join();
        t3.join();
        
        System.out.println("****ALARM TESTING FINISH****");
    }
    
}
